#ifndef RT_SSL_HPP__
#define RT_SSL_HPP__

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>

#include <openssl/md5.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/des.h>

#define MAXDATABUF 1024
// md5 32bit agorithem, not suit for 16bit.
#define MD5LENGTH  16
static int _padding = RSA_PKCS1_PADDING;

// copy from <debug.hpp> [0.2.3]
#define __RT__DEBUG 0
#define nullptr NULL

#ifdef __cplusplus
    #define CharArray(name, n) char name[n]={}
    #define UCharArray(name, n) unsigned char name[n]={}
#else
    #define CharArray(name, n) \
    char name[n]; memset(name,0,n)
    #define UCharArray(name, n) \
    unsigned char name[n]; memset(name,0,n)
#endif

#define _print(note, ...) \
{	if(__RT__DEBUG) \
    printf("-- DEBUG -->> " note "\n",  ##__VA_ARGS__); }

// C-type Exception
#include <setjmp.h>

#define SEXCEPTION Except_SSL
enum ExceptEnum_SSL{
    Base64_Enc_Err = 1,
    Base64_Dec_Err,
    DES_Enc_Err,
    DES_Dec_Err,
    AES_Enc_Err,
    AES_Dec_Err,
    RSA_PubKey_Enc_Err,
    RSA_PrivKey_Dec_Err,
    RSA_PrivKey_Enc_Err,
    RSA_PubKey_Dec_Err,
    Load_Key_Err,
    MD5_File_Err
};

#define ExceptLink_Desciption_Length 64
struct ExceptLink{
    jmp_buf jmp_buffer;
    char description[ExceptLink_Desciption_Length];
};

struct ExceptLink SEXCEPTION;
void _raise(int err_no, const char* description){
    if(0 == err_no){
        printf("In Function Raise() can't set err_no zero.\n");
        abort();
    }
    strncpy(SEXCEPTION.description, description, ExceptLink_Desciption_Length);
    longjmp(SEXCEPTION.jmp_buffer, err_no);
}

#define TRY(module_except) \
    { int jmp_err = setjmp(module_except.jmp_buffer); \
    if (0 == jmp_err)
    // { ExceptLink exception_link; \

#define CATCH(ExceptEnum) \
    else if (ExceptEnum == jmp_err)

#define ELSE else
#define ENDTRY }

/*-----------------------------------------------------------------*\
    SSL Functions Define
\*-----------------------------------------------------------------*/
void _hex_show_char(unsigned char *str, int len){
    if(!__RT__DEBUG)  return;
    if(0 == len)  len = strlen((const char*)str);
    for(int i=0; i<len; i++){
        if(i % 8 == 0)  printf("\n");
        printf(" %02x", str[i]);
    }
    printf("\n");
}

// 需要手动释放内存，default len=0
char* _hex_switch_char(unsigned char *str, int len){
    if(0 == len)  len = strlen((const char*)str);
    char *out = (char*)malloc(sizeof(char)*(len*3+(len-1)/8+2));
    _print("Hex string Length : %ld", sizeof(char)*(len*3+(len-1)/8+2));
    for(int i=0; i<len; i++){
        if(i % 8 == 0)  strcat(out, "\n");
        char tmp[3];  sprintf(tmp, " %02x", str[i]);
        strncat(out, tmp, 3);
    }
    return out;
}

char* encode_base64(char* to, const char* from, int len){
#if 1
    // 精确长度
    int len_to = len%3 ? (len/3+1)*4 : len/3*4;
#else
    // 简便算法，提供富裕长度
    int len_to = (len+2)*4/3;
#endif
    UCharArray(charTo, len_to+1);
    int err = EVP_EncodeBlock(charTo, (unsigned char*)from, len);  // err（字符串长度）不包含末位'\0'
    if(err < 0)  _raise(Base64_Enc_Err, "encode_base64()");
    _print("Base64编码数据：%s", charTo);
    return (char*)memmove(to, (const char*)charTo, len_to+1);  // len_to +1 用于处理末位 '\0'
}

//  缺陷：EVP_DecodeBlock() 无法返回原始字符串长度（由于末位补零）
char* decode_base64(char* to, const char* from, int len){
    // int len_to = len*3/4.0 + 0.999;
    int len_to = len * 3 / 4;  // base64字符串长度为4的整数倍
    UCharArray(charTo, len_to+1);  // len_to +1 用于存放末位 '\0'
    int err = EVP_DecodeBlock(charTo, (unsigned char*)from, len);  // err（字符串长度）不包含末位'\0'
    _print("Base64解码数据：%s", charTo);
    if(err < 0)  _raise(Base64_Dec_Err, "decode_base64()");
    _print("Base64解码字符串长度：%d(%d)", err, len_to);  // 返回err说明：此长度为解密长度，而非原始字符串长度（末位可能补零）
    return (char*)memmove(to, (const char*)charTo, len_to+1);  // len_to +1 用于处理末位 '\0'
}

// 对于明文，len可以置零（NULL），通过strlen()自动计算长度
char* md5_string(char* to, const char *from, int len){
    if(0 == len)  len = strlen(from);
    MD5_CTX ctx;
    UCharArray(digest, MD5LENGTH+1);
    // initialize a hash context
    MD5_Init(&ctx);
    // Update the input string to hash context
    MD5_Update(&ctx, from, len);
    // Compute the hash result
    MD5_Final(digest, &ctx);

/*  // 打印MD5值
    char *hex = _hex_switch_char(digest, MD5LENGTH);
    _print("MD5 [%dbit] 值：%s", MD5LENGTH*2, hex);
    free(hex);  */

    // 将MD5字符串转换为16进制数
    int len_md5_hex = sizeof(char)*(MD5LENGTH*2+1);
    CharArray(charTo, len_md5_hex);
    for(int i=0; i<MD5LENGTH; i++){
        char tmp[2];  sprintf(tmp, "%02x", digest[i]);
        strncat(charTo, tmp, 2);
    }
    _print("MD5 [%dbit] 值：%s", MD5LENGTH*2, charTo);
    return (char*)memmove(to, charTo, len_md5_hex);
}

char* md5_file(char* to, const char *file_path){
	MD5_CTX ctx;
	unsigned char buffer[MAXDATABUF];
	unsigned char digest[MD5LENGTH];

	FILE *fp = fopen(file_path, "rb");
    if(nullptr == fp)  _raise(MD5_File_Err, "md5_file() :: Fail to open md5-file");

	MD5_Init(&ctx);
	int len = 0;
	while ((len = fread(buffer, 1, MAXDATABUF, fp)) > 0){
		MD5_Update(&ctx, buffer, len);
	}
	MD5_Final(digest, &ctx);
    fclose(fp);

    // 将MD5字符串转换为16进制数
    int len_md5_hex = sizeof(char)*(MD5LENGTH*2+1);
    CharArray(charTo, len_md5_hex);
    for(int i=0; i<MD5LENGTH; i++){
        char tmp[2];  sprintf(tmp, "%02x", digest[i]);
        strncat(charTo, tmp, 2);
    }
    _print("MD5 [%dbit] 值：%s", MD5LENGTH*2, charTo);
    return (char*)memmove(to, charTo, len_md5_hex);
}

char* encrypt_des(char* to, const char* from, int len_from, const char* strkey){
    DES_key_schedule _key;
    DES_cblock s_des;
    DES_string_to_key(strkey, &s_des);
    DES_set_key_unchecked(&s_des, &_key);

    int remainder = len_from % 8;
    int len_to = remainder ? (len_from/8+1)*8 : len_from;
    CharArray(charTo, len_to+1);  // 也可以不引入charTo变量，直接赋值给 char* to
    const_DES_cblock des_from;
    DES_cblock des_get;
    for (int i=0; i < len_from/8; i++){
        memmove(des_from, from + i*8, 8);
        DES_ecb_encrypt(&des_from, &des_get, &_key, DES_ENCRYPT);
        memmove(charTo + i*8, des_get, 8);
    }

    if (remainder){
        memset(des_from, 0, 8);
        memmove(des_from, from + len_from - remainder, remainder);
        DES_ecb_encrypt(&des_from, &des_get, &_key, DES_ENCRYPT);
        memmove(charTo + len_from - remainder, des_get, 8);
    }
    _print("DES加密数据：%s", charTo);

    // 加密后，密文中可能存在\0字符，故需转换为base64编码
#if 0
    int len_base64 = len_to%3 ? (len_to/3+1)*4 : len_to/3*4;
    CharArray(charBase64, len_base64+1);
    int err = EVP_EncodeBlock(charBase64, (unsigned char*)charTo, len_to);  // err（字符串长度）不包含末位'\0'
    if(err < 0)  _raise(Base64_Enc_Err, "encrypt_des()");
    return (char*)memmove(to, (const char*)charBase64, len_base64+1);  // len_base64 +1 用于处理末位 '\0'
#else
    return encode_base64(to, (const char*)charTo, len_to);
#endif
}

char* decrypt_des(char* to, const char* from, int len, const char* strkey){
    DES_key_schedule _key;
    DES_cblock s_des;
    DES_string_to_key(strkey, &s_des);
    DES_set_key_unchecked(&s_des, &_key);

    // base64解码
    int len_base64 = len/4*3;
    CharArray(cipher, len_base64+1);
#if 0
    int err = EVP_DecodeBlock((unsigned char*)cipher, (const unsigned char*)from, len);  // 此err包含NUL
#else
    decode_base64(cipher, from, len);
#endif
    int len_des = len_base64 / 8 * 8 + 1;
    CharArray(charTo, len_des);  // 也可以不引入charTo变量，直接赋值给 char* to
    const_DES_cblock des_from;
    DES_cblock des_get;
    for (int i=0; i < len_base64/8; i++){  // len_base64 可能不是8的整数倍（如果base64加密时末位补'='），直接舍弃即可
        memmove(des_from, cipher + i*8, 8);
        DES_ecb_encrypt(&des_from, &des_get, &_key, DES_DECRYPT);
        memmove(charTo + i*8, des_get, 8);
    }

    _print("DES解密数据：%s", charTo);
    return (char*)memmove(to, (const char*)charTo, len_des);  // len_base64 +1 用于处理末位 '\0'
}


/*-----------------------------------------------------------------*\
    RSA Encrypt
\*-----------------------------------------------------------------*/

RSA* rsa_load_keyfile(const char* file_path, bool isPubkey){
    FILE *fp = fopen(file_path, "r");
    if(nullptr == fp)  _raise(Load_Key_Err, "rsa_load_keyfile() :: Fail to open key-file");

    RSA* key = isPubkey ? PEM_read_RSA_PUBKEY(fp, nullptr, nullptr, nullptr) :
                          PEM_read_RSAPrivateKey(fp, nullptr, nullptr, nullptr);
    fclose(fp);
    if(nullptr == key)  _raise(Load_Key_Err, "rsa_load_keyfile() :: Fail to Read PEM-Key");
    return key;
}

bool rsa_free_keyfile(RSA* key){
    if (key != nullptr)  RSA_free(key);
    return true;
}

char* rsa_error(){
    unsigned long err = ERR_peek_last_error();  // ERR_get_error
    if(err != 0){
        char *estring = nullptr;
        ERR_error_string(err, estring);
        printf("OpenSSL Error : %s\n", estring);
        return estring;
    }
    return nullptr;
}

// 注意：公钥加密生成的加密字符串为随机值，密文并不一致，但不影响解密
char* encrypt_rsa_pubkey(char* to, const char* from, int len_from, RSA* key){
    if(key == nullptr)
        _raise(RSA_PubKey_Enc_Err, "encrypt_rsa_pubkey() :: NULL key");
    int rsa_size = RSA_size(key);
    int size_padding = rsa_size;

    switch(_padding){
        case RSA_NO_PADDING:
            break;  // size_padding = RSA_size(key);
        case RSA_PKCS1_PADDING:
            size_padding -= 11;  break;
        case RSA_PKCS1_OAEP_PADDING:
            size_padding -= 41;  break;
        default:
            _raise(RSA_PubKey_Enc_Err, "encrypt_rsa_pubkey() :: Unkown Padding type");
    }
    // 验证加密方式
    if(len_from >= size_padding)
        _raise(RSA_PubKey_Enc_Err, "encrypt_rsa_pubkey() :: PADDING length >= flen");

    unsigned char charTo[rsa_size];
    int err = RSA_public_encrypt(len_from, (unsigned char *)from, charTo, key, _padding);
    if(-1 == err)
        _raise(RSA_PubKey_Enc_Err, rsa_error());

    // 通过 base64 编码密文
    return encode_base64(to, (const char*)charTo, err);
}

char* decrypt_rsa_privkey(char* to, const char* from, int len_from, RSA* key){
    // base64解码
    int len_base64 = len_from/4*3;
    CharArray(cipher, len_base64+1);
#if 0
    int err = EVP_DecodeBlock((unsigned char*)cipher, (const unsigned char*)from, len);  // 此err包含NUL
#else
    decode_base64(cipher, from, len_from);
#endif
    if(key == nullptr)
        _raise(RSA_PrivKey_Dec_Err, "decrypt_rsa_privkey() :: NULL key");
    int rsa_size = RSA_size(key);

    UCharArray(charTo, rsa_size);
    // 如运行正确，则返回解密字符串长度
    int err = RSA_private_decrypt(rsa_size, (unsigned char *)cipher, charTo, key, _padding);
    if(-1 == err)
        _raise(RSA_PrivKey_Dec_Err, rsa_error());

    _print("RSA解密数据：%s", charTo);
    return (char*)memmove(to, (const char*)charTo, err+1);  // len_base64 +1 用于处理末位 '\0'
}

// 私钥加密生成的密文是一致的
char* encrypt_rsa_privkey(char* to, const char* from, int len_from, RSA* key){
    if(key == nullptr)
        _raise(RSA_PrivKey_Enc_Err, "encrypt_rsa_privkey() :: NULL key");
    int rsa_size = RSA_size(key);
    int size_padding = rsa_size;

    switch(_padding){
        case RSA_NO_PADDING:
            break;  // size_padding = RSA_size(key);
        case RSA_PKCS1_PADDING:
            size_padding -= 11;  break;
        case RSA_PKCS1_OAEP_PADDING:
            size_padding -= 41;  break;
        default:
            _raise(RSA_PrivKey_Enc_Err, "encrypt_rsa_privkey() :: Unkown Padding type");
    }
    // 验证加密方式
    if(len_from >= size_padding)
        _raise(RSA_PrivKey_Enc_Err, "encrypt_rsa_privkey() :: PADDING length >= flen");

    unsigned char charTo[rsa_size];
    int err = RSA_private_encrypt(len_from, (unsigned char *)from, charTo, key, _padding);
    if(-1 == err)
        _raise(RSA_PrivKey_Enc_Err, rsa_error());

    // 通过 base64 编码密文
    return encode_base64(to, (const char*)charTo, err);
}

char* decrypt_rsa_pubkey(char* to, const char* from, int len_from, RSA* key){
    // base64解码
    int len_base64 = len_from/4*3;
    CharArray(cipher, len_base64+1);
#if 0
    int err = EVP_DecodeBlock((unsigned char*)cipher, (const unsigned char*)from, len);  // 此err包含NUL
#else
    decode_base64(cipher, from, len_from);
#endif
    if(key == nullptr)
        _raise(RSA_PubKey_Dec_Err, "decrypt_rsa_pubkey() :: NULL key");
    int rsa_size = RSA_size(key);

    UCharArray(charTo, rsa_size);
    // 如运行正确，则返回解密字符串长度
    int err = RSA_public_decrypt(rsa_size, (unsigned char *)cipher, charTo, key, _padding);
    if(-1 == err)
        _raise(RSA_PubKey_Dec_Err, rsa_error());

    _print("RSA解密数据：%s", charTo);
    return (char*)memmove(to, (const char*)charTo, err+1);  // len_base64 +1 用于处理末位 '\0'
}

#endif  // RT_SSL_HPP__
